// File: jmcomic-core/src/main/java/dev/jukomu/jmcomic/config/JmModuleConfig.java
package dev.jukomu.jmcomic.config;

import dev.jukomu.common.base.registry.ComponentRegistry;
import dev.jukomu.common.postman.Postman;
import dev.jukomu.common.util.net.IResponse;
import dev.jukomu.common.util.net.ProxyBuilder;
import dev.jukomu.common.util.system.SystemUtils;
import dev.jukomu.jmcomic.client.JmcomicClient;
import dev.jukomu.jmcomic.client.impl.JmApiClient;
import dev.jukomu.jmcomic.client.impl.JmHtmlClient;
import dev.jukomu.jmcomic.downloader.JmDownloader;
import dev.jukomu.jmcomic.entity.JmAlbumDetail;
import dev.jukomu.jmcomic.entity.JmImageDetail;
import dev.jukomu.jmcomic.entity.JmPhotoDetail;
import dev.jukomu.jmcomic.option.JmOption;
import dev.jukomu.jmcomic.plugin.JmOptionPlugin;
import dev.jukomu.jmcomic.util.JmcomicText;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;

/**
 * 对应Python的JmModuleConfig，包含模块级别的通用配置和常量。
 * 这是一个静态配置类，不应被实例化。
 */
public final class JmModuleConfig {

    // 静态初始化块，在类加载时执行
    static {
        // 在类加载时注册JmcomicClient的实现
        registerClient(JmHtmlClient.class);
        registerClient(JmApiClient.class);
        // 如果有其他JmcomicClient实现，也在这里注册
    }

    // 网站相关
    public static final String PROT = "https://";
    public static final String JM_REDIRECT_URL = PROT + "jm365.work/3YeBdF"; // 永久網域，怕走失的小伙伴收藏起来
    public static final String JM_PUB_URL = PROT + "jmcomic-fb.vip";
    public static final String JM_CDN_IMAGE_URL_TEMPLATE = PROT + "cdn-msp.{domain}/media/photos/{photo_id}/{index:05}{suffix}"; // index 从1开始
    public static final List<String> JM_IMAGE_SUFFIX = Collections.unmodifiableList(Arrays.asList(".jpg", ".webp", ".png", ".gif"));

    // JM的异常网页内容
    public static final Map<String, String> JM_ERROR_RESPONSE_TEXT = Collections.unmodifiableMap(new HashMap<String, String>() {{
        put("Could not connect to mysql! Please check your database settings!", "禁漫服务器内部报错");
        put("Restricted Access!", "禁漫拒绝你所在ip地区的访问，你可以选择: 换域名/换代理");
    }});

    // JM的异常HTTP状态码
    public static final Map<Integer, String> JM_ERROR_STATUS_CODE = Collections.unmodifiableMap(new HashMap<Integer, String>() {{
        put(403, "ip地区禁止访问/爬虫被识别");
        put(500, "500: 禁漫服务器内部异常（可能是服务器过载，可以切换ip或稍后重试）");
        put(520, "520: Web server is returning an unknown error (禁漫服务器内部报错)");
        put(524, "524: The origin web server timed out responding to this request. (禁漫服务器处理超时)");
    }});

    // 分页大小
    public static final int PAGE_SIZE_SEARCH = 80;
    public static final int PAGE_SIZE_FAVORITE = 20;

    // 图片分割相关
    public static final Map<String, String> SCRAMBLE_CACHE = new ConcurrentHashMap<>(); // 线程安全的缓存Map

    // 当本子没有作者名字时，顶替作者名字
    public static final String DEFAULT_AUTHOR = "default_author";

    // Cookies，目前只在移动端使用，因为移动端请求接口须携带，但不会校验cookies的内容。
    // 在Java中，这些会作为Postman的metaData一部分传递给HttpClient
    public static Map<String, Object> APP_COOKIES = null; // 可变，由登录或自动获取更新

    // 移动端图片域名 (shuffled)
    public static List<String> DOMAIN_IMAGE_LIST = Collections.unmodifiableList(
            shuffleList(Arrays.asList(
                    "cdn-msp.jmapiproxy1.cc",
                    "cdn-msp.jmapiproxy2.cc",
                    "cdn-msp2.jmapiproxy2.cc",
                    "cdn-msp3.jmapiproxy2.cc",
                    "cdn-msp.jmapinodeudzn.net",
                    "cdn-msp3.jmapinodeudzn.net"
            ))
    );

    // 移动端API域名 (shuffled)
    public static List<String> DOMAIN_API_LIST = Collections.unmodifiableList(
            shuffleList(Arrays.asList(
                    "www.cdnmhwscc.vip",
                    "www.cdnplaystation6.club",
                    "www.cdnplaystation6.org",
                    "www.cdnuc.vip",
                    "www.cdn-mspjmapiproxy.xyz"
            ))
    );

    // 获取最新移动端API域名的地址
    public static final String API_URL_DOMAIN_SERVER = PROT + "jmappc01-1308024008.cos.ap-guangzhou.myqcloud.com/server-2024.txt";

    // App端请求头模板
    public static final Map<String, String> APP_HEADERS_TEMPLATE = Collections.unmodifiableMap(new HashMap<String, String>() {{
        put("Accept-Encoding", "gzip, deflate");
        put("user-agent", "Mozilla/5.0 (Linux; Android 9; V1938CT Build/PQ3A.190705.11211812; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/91.0.4472.114 Safari/537.36");
    }});

    // App端图片请求头
    public static final Map<String, String> APP_HEADERS_IMAGE = Collections.unmodifiableMap(new HashMap<String, String>() {{
        put("Accept", "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8");
        put("X-Requested-With", "com.jiaohua_browser");
        // Referer will be dynamically set based on DOMAIN_API_LIST[0] in new_html_headers or client
        put("Accept-Language", "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7");
    }});

    // 网页端请求头模板
    public static final Map<String, String> HTML_HEADERS_TEMPLATE = Collections.unmodifiableMap(new HashMap<String, String>() {{
        put("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7");
        put("accept-language", "zh-CN,zh;q=0.9");
        put("cache-control", "no-cache");
        put("dnt", "1");
        put("pragma", "no-cache");
        put("priority", "u=0, i");
        // Referer will be dynamically set, default is https://18comic.vip/
        put("sec-ch-ua", "\"Chromium\";v=\"124\", \"Google Chrome\";v=\"124\", \"Not-A.Brand\";v=\"99\"");
        put("sec-ch-ua-mobile", "?0");
        put("sec-ch-ua-platform", "\"Windows\"");
        put("sec-fetch-dest", "document");
        put("sec-fetch-mode", "navigate");
        put("sec-fetch-site", "none");
        put("sec-fetch-user", "?1");
        put("upgrade-insecure-requests", "1");
        put("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36");
    }});

    // 网页端域名配置
    // 无需配置，默认为null，需要的时候会发起请求获得
    public static String DOMAIN_HTML = null; // 可动态获取并缓存
    public static List<String> DOMAIN_HTML_LIST = null; // 可动态获取并缓存

    // 模块级别的可重写类配置（通常通过ComponentRegistry管理）
    // 在Java中，这些通常是Class<?>类型，通过工厂方法获取实例
    public static Class<?> CLASS_DOWNLOADER = null; // 默认为null，将使用默认下载器类
    public static Class<?> CLASS_OPTION = null;     // 默认为null，将使用默认Option类
    public static Class<?> CLASS_ALBUM = null;      // 默认为null，将使用默认AlbumDetail类
    public static Class<?> CLASS_PHOTO = null;      // 默认为null，将使用默认PhotoDetail类
    public static Class<?> CLASS_IMAGE = null;      // 默认为null，将使用默认ImageDetail类

    // 插件注册表：key -> 插件实现类
    public static final Map<String, Class<?>> REGISTRY_PLUGIN = new ConcurrentHashMap<>();
    // 异常监听器：key(异常类) -> value(监听函数)
    public static final Map<Class<?>, Function<Exception, Void>> REGISTRY_EXCEPTION_LISTENER = new ConcurrentHashMap<>();

    // 执行日志的函数
    // 默认为Slf4jLogger，它会使用SLF4J框架进行日志记录
    public static JmcomicLogger EXECUTOR_LOG = new Slf4jLogger();

    // 使用固定时间戳
    public static volatile boolean FLAG_USE_FIX_TIMESTAMP = true;
    // 移动端Client初始化Cookies
    public static volatile boolean FLAG_API_CLIENT_REQUIRE_COOKIES = true;
    // 自动更新禁漫API域名
    public static volatile boolean FLAG_API_CLIENT_AUTO_UPDATE_DOMAIN = true;
    public static final AtomicBoolean FLAG_API_CLIENT_AUTO_UPDATE_DOMAIN_DONE = new AtomicBoolean(false); // 确保只执行一次
    // Log开关标记
    public static volatile boolean FLAG_ENABLE_JM_LOG = true;
    // Log时解码URL
    public static volatile boolean FLAG_DECODE_URL_WHEN_LOGGING = true;
    // 当内置的版本号落后时，使用最新的禁漫App版本号
    public static volatile boolean FLAG_USE_VERSION_NEWER_IF_BEHIND = true;

    // 关联dir_rule的自定义字段与对应的处理函数
    // 例如: AFIELD_ADVICE.put("myname", album -> "自定义名称")
    public static final Map<String, Function<JmAlbumDetail, String>> AFIELD_ADVICE = new ConcurrentHashMap<>();
    public static final Map<String, Function<JmPhotoDetail, String>> PFIELD_ADVICE = new ConcurrentHashMap<>();

    // 当发生文件名过长错误时，将文件名限制在指定字符以内
    public static final int VAR_FILE_NAME_LENGTH_LIMIT = 100;

    // Option 相关的默认配置版本
    public static final String JM_OPTION_VER = "2.1";

    // 默认Client实现类型
    public static String DEFAULT_CLIENT_IMPL = "api"; // 默认使用API客户端

    // 默认Client缓存设置
    public static Boolean DEFAULT_CLIENT_CACHE = null; // 默认关闭客户端缓存。null表示Option配置，false明确关闭，true明确开启

    // 默认代理配置，使用系统代理
    public static Map<String, String> DEFAULT_PROXIES = ProxyBuilder.systemProxy();

    // 用于 get_html_domain() 的缓存，使用AtomicReference来保证线程安全和CAS更新
    private static final AtomicReference<String> HTML_DOMAIN_CACHE = new AtomicReference<>(null);
    // 用于 get_html_domain_all() 的缓存
    private static final AtomicReference<List<String>> HTML_DOMAIN_LIST_CACHE = new AtomicReference<>(null);

    // 确保不可实例化
    private JmModuleConfig() {
    }

    /**
     * 获取下载器类。
     *
     * @return 下载器Class对象。
     */
    /**
     * 获取下载器类。
     *
     * @return 下载器Class对象。
     */
    public static Class<?> getDownloaderClass() {
        if (CLASS_DOWNLOADER != null) {
            return CLASS_DOWNLOADER;
        }
        return JmDownloader.class; // 返回默认下载器类
    }

    /**
     * 获取Option类。
     *
     * @return OptionClass对象。
     */
    public static Class<?> getOptionClass() {
        if (CLASS_OPTION != null) {
            return CLASS_OPTION;
        }
        return JmOption.class; // 返回默认Option类
    }

    /**
     * 获取AlbumDetail类。
     *
     * @return AlbumDetail Class对象。
     */
    public static Class<?> getAlbumClass() {
        if (CLASS_ALBUM != null) {
            return CLASS_ALBUM;
        }
        // 这里会引用jmcomic.entity包
        return JmAlbumDetail.class; // Assume JmAlbumDetail is the default
    }

    /**
     * 获取PhotoDetail类。
     *
     * @return PhotoDetail Class对象。
     */
    public static Class<?> getPhotoClass() {
        if (CLASS_PHOTO != null) {
            return CLASS_PHOTO;
        }
        // 这里会引用jmcomic.entity包
        return JmPhotoDetail.class; // Assume JmPhotoDetail is the default
    }

    /**
     * 获取ImageDetail类。
     *
     * @return ImageDetail Class对象。
     */
    public static Class<?> getImageClass() {
        if (CLASS_IMAGE != null) {
            return CLASS_IMAGE;
        }
        // 这里会引用jmcomic.entity包
        return JmImageDetail.class; // Assume JmImageDetail is the default
    }

    /**
     * 根据客户端键获取客户端实现类。
     *
     * @param clientKey 客户端键名。
     * @return 客户端实现Class对象。
     * @throws AssertionError 如果未找到对应的客户端实现类。
     */
    @SuppressWarnings("unchecked")
    public static Class<? extends JmcomicClient> getClientImplClass(String clientKey) {
        // **关键修正点**: 从 ComponentRegistry 全局注册表中查找
        return ComponentRegistry.getImplClass(JmcomicClient.class, clientKey);
    }


    /**
     * 获取默认的Option配置Map。
     *
     * @return 默认配置Map。
     */
    public static Map<String, Object> optionDefaultDict() {
        Map<String, Object> optionDict = new HashMap<>();

        // 填充默认值，与Python代码保持一致
        optionDict.put("log", null); // Log开关，null表示跟随FLAG_ENABLE_JM_LOG
        optionDict.put("dir_rule", new HashMap<String, Object>() {{
            put("rule", "Bd_Pname");
            put("base_dir", null); // 默认当前工作目录
        }});
        optionDict.put("download", new HashMap<String, Object>() {{
            put("cache", true);
            put("image", new HashMap<String, Object>() {{
                put("decode", true);
                put("suffix", null);
            }});
            put("threading", new HashMap<String, Object>() {{
                put("image", 30);
                put("photo", null); // 默认CPU核心数
            }});
        }});
        optionDict.put("client", new HashMap<String, Object>() {{
            put("cache", null); // 默认跟随DEFAULT_CLIENT_CACHE
            put("domain", new HashMap<>()); // Map for domain configurations per client type
            put("postman", new HashMap<String, Object>() {{
                put("type", "curl_cffi"); // 默认使用curl_cffi模拟的OkHttp
                put("meta_data", new HashMap<String, Object>() {{
                    put("impersonate", "chrome"); // Python默认impersonate
                    put("headers", null); // 默认根据客户端类型生成
                    put("proxies", null); // 默认使用系统代理
                }});
            }});
            put("impl", null); // 默认跟随DEFAULT_CLIENT_IMPL
            put("retry_times", 5);
        }});
        optionDict.put("plugins", new HashMap<String, Object>() {{
            put("valid", "log"); // 如果插件抛出参数校验异常，默认只log
        }});

        // 后处理默认值
        // Log开关
        if (optionDict.get("log") == null) {
            optionDict.put("log", FLAG_ENABLE_JM_LOG);
        }

        // dir_rule.base_dir
        Map<String, Object> dirRule = (Map<String, Object>) optionDict.get("dir_rule");
        if (dirRule.get("base_dir") == null) {
            dirRule.put("base_dir", System.getProperty("user.dir")); // Java获取当前工作目录
        }

        // client cache
        Map<String, Object> clientConfig = (Map<String, Object>) optionDict.get("client");
        if (clientConfig.get("cache") == null) {
            clientConfig.put("cache", DEFAULT_CLIENT_CACHE);
        }

        // client impl
        if (clientConfig.get("impl") == null) {
            clientConfig.put("impl", DEFAULT_CLIENT_IMPL);
        }

        // postman proxies
        Map<String, Object> postmanConfig = (Map<String, Object>) clientConfig.get("postman");
        Map<String, Object> postmanMetaData = (Map<String, Object>) postmanConfig.get("meta_data");
        if (postmanMetaData.get("proxies") == null) {
            postmanMetaData.put("proxies", DEFAULT_PROXIES);
        }

        // threading photo
        Map<String, Object> downloadConfig = (Map<String, Object>) optionDict.get("download");
        Map<String, Object> threadingConfig = (Map<String, Object>) downloadConfig.get("threading");
        if (threadingConfig.get("photo") == null) {
            threadingConfig.put("photo", Runtime.getRuntime().availableProcessors()); // CPU核心数
        }

        return optionDict;
    }

    /**
     * 注册插件类。
     *
     * @param pluginClass 插件实现类。
     * @throws AssertionError 如果插件类缺少`plugin_key`字段。
     */
    public static void registerPlugin(Class<?> pluginClass) {
        ComponentRegistry.registerComponent(
                JmOptionPlugin.class, // 插件接口
                "PLUGIN_KEY", // 插件键字段名
                Collections.singletonList(pluginClass), // 单个类
                true // 要求必须有键字段
        );
    }

    /**
     * 注册客户端类。
     *
     * @param clientClass 客户端实现类。
     * @throws AssertionError 如果客户端类缺少`CLIENT_KEY`字段。
     */
    public static void registerClient(Class<?> clientClass) {
        // **关键修正点**: 确保注册的接口是 JmcomicClient.class
        ComponentRegistry.registerComponent(
                JmcomicClient.class, // 注册到 JmcomicClient 接口下
                "CLIENT_KEY", // 客户端键字段名
                Collections.singletonList(clientClass), // 单个类
                true // 要求必须有键字段
        );
    }

    /**
     * 注册异常监听器。
     *
     * @param exceptionType 异常类型。
     * @param listener      监听函数，接收异常对象。
     */
    public static void registerExceptionListener(Class<? extends Exception> exceptionType, Function<Exception, Void> listener) {
        REGISTRY_EXCEPTION_LISTENER.put(exceptionType, listener);
    }

    /**
     * 获取一个可用的禁漫网页域名。
     * 第一次调用会通过HTTP请求获取，之后会缓存。
     *
     * @param postman Postman实例，用于发起HTTP请求。
     * @return 可用的禁漫网页域名。
     */
    public static String getHtmlDomain(Postman postman) throws Exception {
        // 使用AtomicReference来保证线程安全的单次初始化和缓存
        String domain = HTML_DOMAIN_CACHE.get();
        if (domain == null) {
            synchronized (HTML_DOMAIN_CACHE) { // 双重检查锁定
                domain = HTML_DOMAIN_CACHE.get();
                if (domain == null) {
                    domain = JmcomicText.parseToJmDomain(getHtmlUrl(postman));
                    HTML_DOMAIN_CACHE.set(domain);
                }
            }
        }
        return domain;
    }

    public static String getHtmlDomain() throws Exception {
        return getHtmlDomain(newPostman(true, null));
    }

    /**
     * 获取禁漫的永久网域并重定向到的可用网址。
     *
     * @param postman Postman实例。
     * @return 可用的禁漫网址。
     * @throws Exception 如果请求失败。
     */
    public static String getHtmlUrl(Postman postman) throws Exception {
        Postman redirectingPostman = postman.withRedirectCatching();
        // Python的实现是直接返回Location头，这里我们让withRedirectCatching返回最终响应的URL
        String url = redirectingPostman.get(JM_REDIRECT_URL).getUrl();
        jmLog("module.html_url", "获取禁漫网页URL: [" + JM_REDIRECT_URL + "] → [" + url + "]");
        return url;
    }

    /**
     * 获取所有禁漫网页域名。
     * 第一次调用会通过HTTP请求获取，之后会缓存。
     *
     * @param postman Postman实例。
     * @return 禁漫网页域名列表。
     * @throws Exception 如果请求失败。
     */
    public static List<String> getHtmlDomainAll(Postman postman) throws Exception {
        List<String> domainList = HTML_DOMAIN_LIST_CACHE.get();
        if (domainList == null) {
            synchronized (HTML_DOMAIN_LIST_CACHE) { // 双重检查锁定
                domainList = HTML_DOMAIN_LIST_CACHE.get();
                if (domainList == null) {
                    IResponse resp = postman.get(JM_PUB_URL);
                    if (resp.getHttpCode() != 200) {
                        throw new Exception("Request failed to get all HTML domains, HTTP status code: " + resp.getHttpCode());
                    }
                    domainList = dev.jukomu.jmcomic.util.JmcomicText.analyseJmPubHtml(resp.getText());
                    HTML_DOMAIN_LIST_CACHE.set(domainList);
                }
            }
        }
        jmLog("module.html_domain_all", "获取禁漫网页全部域名: [" + JM_PUB_URL + "] → " + domainList);
        return domainList;
    }

    /**
     * 通过GitHub页面获取所有禁漫网页域名。
     *
     * @param postman    Postman实例。
     * @param template   URL模板，例如 "https://jmcmomic.github.io/go/{}.html"。
     * @param indexRange 索引范围，例如 (300, 309)。
     * @return 禁漫网页域名Set。
     * @throws Exception 如果请求失败。
     */
    public static java.util.Set<String> getHtmlDomainAllViaGithub(Postman postman, String template, int[] indexRange) throws Exception {
        java.util.Set<String> domainSet = ConcurrentHashMap.newKeySet(); // 线程安全的Set

        dev.jukomu.common.base.concurrent.MultiTaskExecutor executor = new dev.jukomu.common.base.concurrent.MultiTaskExecutor();
        List<String> urlsToFetch = new ArrayList<>();
        for (int i = indexRange[0]; i <= indexRange[1]; i++) {
            urlsToFetch.add(String.format(template, i));
        }

        executor.launch(urlsToFetch, url -> {
            try {
                IResponse resp = postman.get(url, Collections.singletonMap("allow_redirects", false));
                if (resp.getHttpCode() == 200) {
                    for (String domain : dev.jukomu.jmcomic.util.JmcomicText.analyseJmPubHtml(resp.getText())) {
                        if (domain.startsWith("jm365")) {
                            continue;
                        }
                        domainSet.add(domain);
                    }
                } else {
                    jmLog("module.github_domain", "Failed to fetch domain from: " + url + ", status: " + resp.getHttpCode());
                }
            } catch (Exception e) {
                jmLog("module.github_domain.error", "Error fetching domain from " + url + ": " + e.getMessage());
                SystemUtils.printStackTrace();
            }
        }, true); // Wait for all tasks to complete

        return domainSet;
    }

    /**
     * 获取通过GitHub页面获取所有禁漫网页域名，使用默认模板和范围。
     *
     * @param postman Postman实例。
     * @return 禁漫网页域名Set。
     * @throws Exception 如果请求失败。
     */
    public static java.util.Set<String> getHtmlDomainAllViaGithub(Postman postman) throws Exception {
        return getHtmlDomainAllViaGithub(postman, "https://jmcmomic.github.io/go/{}.html", new int[]{300, 309});
    }

    /**
     * 创建新的HTML请求头Map。
     *
     * @param domain 域名。
     * @return 请求头Map。
     */
    public static Map<String, String> newHtmlHeaders(String domain) {
        Map<String, String> headers = new HashMap<>(HTML_HEADERS_TEMPLATE);
        headers.put("authority", domain);
        headers.put("origin", PROT + domain);
        headers.put("referer", PROT + domain + "/"); // Python's default referer for 18comic.vip
        return Collections.unmodifiableMap(headers);
    }

    /**
     * 创建新的HTML请求头Map，使用默认域名"18comic.vip"。
     *
     * @return 请求头Map。
     */
    public static Map<String, String> newHtmlHeaders() {
        return newHtmlHeaders("18comic.vip");
    }

    /**
     * 获取固定时间戳、Token和TokenParam。
     * 用于API请求。
     *
     * @return 包含时间戳、Token和TokenParam的数组。
     */
    public static Object[] getFixTsTokenTokenparam() {
        long ts = dev.jukomu.common.util.time.TimeUtils.timestamp();
        String token = dev.jukomu.jmcomic.util.JmCryptoUtils.tokenAndTokenparam(ts, null, null)[0];
        String tokenparam = dev.jukomu.jmcomic.util.JmCryptoUtils.tokenAndTokenparam(ts, null, null)[1];
        return new Object[]{ts, token, tokenparam};
    }

    /**
     * 记录禁漫模块的日志。
     *
     * @param topic 日志主题。
     * @param msg   日志消息。
     */
    public static void jmLog(String topic, String msg) {
        if (FLAG_ENABLE_JM_LOG) {
            EXECUTOR_LOG.log(topic, msg);
        }
    }

    /**
     * 禁用禁漫模块的日志。
     */
    public static void disableJmLog() {
        FLAG_ENABLE_JM_LOG = false;
    }

    /**
     * 创建一个新的Postman实例。
     *
     * @param session 是否使用Session Postman。
     * @param kwargs  额外参数。
     * @return Postman实例。
     */
    public static Postman newPostman(boolean session, Map<String, Object> kwargs) {
        Map<String, Object> finalKwargs = new HashMap<>();
        if (kwargs != null) {
            finalKwargs.putAll(kwargs);
        }

        // 默认impersonate为chrome
        finalKwargs.putIfAbsent("impersonate", "chrome");
        // 默认headers
        finalKwargs.putIfAbsent("headers", newHtmlHeaders());
        // 默认proxies
        finalKwargs.putIfAbsent("proxies", DEFAULT_PROXIES);

        // 调用Postmans工厂方法创建
        if (session) {
            return dev.jukomu.common.postman.impl.Postmans.newSession(finalKwargs);
        }
        return dev.jukomu.common.postman.impl.Postmans.newPostman(finalKwargs);
    }

    /**
     * 创建一个新的Postman实例，不使用Session。
     *
     * @param kwargs 额外参数。
     * @return Postman实例。
     */
    public static Postman newPostman(Map<String, Object> kwargs) {
        return newPostman(false, kwargs);
    }

    /**
     * 创建一个新的Postman实例，不使用Session且不带额外参数。
     *
     * @return Postman实例。
     */
    public static Postman newPostman() {
        return newPostman(false, null);
    }


    // --- 辅助方法 ---
    private static <T> List<T> shuffleList(List<T> list) {
        Collections.shuffle(list, new java.security.SecureRandom());
        return list;
    }

    /**
     * JmcomicLogger接口，用于封装日志输出。
     */
    public interface JmcomicLogger {
        void log(String topic, String msg);
    }

    /**
     * 基于SLF4J的JmcomicLogger实现。
     */
    public static class Slf4jLogger implements JmcomicLogger {
        private static final Logger logger = LoggerFactory.getLogger("jmcomic");

        @Override
        public void log(String topic, String msg) {
            logger.info("[{}] {}", topic, msg);
        }
    }
}